package com.coincalf.wallet.blockchain;

import com.coincalf.framework.enums.TxStateEnum;
import com.coincalf.wallet.blockchain.bean.TransInfo;
import com.coincalf.wallet.blockchain.client.Web3jSingleton;
import com.coincalf.wallet.blockchain.util.AESUtil;
import com.coincalf.wallet.blockchain.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.datatypes.Function;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.response.Transaction;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Optional;

import static java.math.BigDecimal.ROUND_HALF_UP;


@Slf4j
public class Erc20TokenService {

    private String contractAddress;
    private static Erc20TokenService client = null;

    public static Erc20TokenService getTokenService(String contractAddress) {
        if (client == null) {
            synchronized (Erc20TokenService.class) {
                if (null == client) {
                    client = new Erc20TokenService();
                }
            }
        }
        client.contractAddress = contractAddress;
        return client;
    }

    /**
     * 查询ERC20代币余额
     *
     * @param address 钱包地址
     * @return
     */
    public BigDecimal getBalance(String address) {
        try {
            String value = Web3jSingleton.getAdmin().ethCall(org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction(address,
                    contractAddress, BlockConstant.DATA_PREFIX + address.substring(2)), DefaultBlockParameterName.PENDING).send().getValue();
            if ("0x".equals(value)) {
                return BigDecimal.ZERO;
            }

            String s = new BigInteger(value.substring(2), 16).toString();
            BigDecimal result;
            if (this.contractAddress.equalsIgnoreCase(BlockConstant.USDT_ERC20_ADDRESS)) {
                result = new BigDecimal(s).divide(BigDecimal.valueOf(1000000), 6, RoundingMode.HALF_DOWN);
            } else {
                result = new BigDecimal(s).divide(BlockConstant.WEI, 6, RoundingMode.HALF_DOWN);
            }
            return result;
        } catch (Exception e) {
            log.error("获取ERC20代币余额异常 e={}", e.getMessage());
        }
        return null;
    }

    /**
     * ERC20 转帐
     *
     * @param bean
     * @return
     */
    public TransInfo trans(TransInfo bean) {
        TransInfo transInfo = new TransInfo();
        log.info("ERC20交易开始 -> " +
                "bean={}", JsonUtils.writeValueAsString(bean));
        BigDecimal amount = bean.getAmount();
        BigDecimal fee = bean.getFee();
        String privateKey = AESUtil.decrypt(bean.getPrivateKey(), BlockConstant.AES_KEY);
        try {
            //获取nonce
            BigInteger nonce = Web3jSingleton.getWeb3j().ethGetTransactionCount(bean.getFromAddress(), DefaultBlockParameterName.PENDING).send().getTransactionCount();
            String txHash = sendErc20Token(nonce, bean.getToAddress(), amount, fee, privateKey, this.contractAddress);
            log.info("txId={}", txHash);
            if (StringUtils.isEmpty(txHash)) {
                log.error("ERC20交易失败");
                transInfo.setStatus(TxStateEnum.FAIL.getStatus());
                transInfo.setNonce(nonce.longValue());
            } else {
                transInfo.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                transInfo.setNonce(nonce.longValue());
                transInfo.setAmount(bean.getAmount());
                transInfo.setTxHash(txHash);
            }
        } catch (Exception e) {
            throw new BlockException(e.getMessage());
        }
        return transInfo;
    }

    /**
     * 复用EthBlockChainService.query()
     *
     * @param txId
     * @return
     */
    public static TransInfo query(String txId) {
        TransInfo bean = new TransInfo();
        bean.setTxHash(txId);
        try {
            Transaction transaction;
            Admin admin = Web3jSingleton.getAdmin();
            transaction = admin.ethGetTransactionByHash(txId).send().getTransaction().orElse(null);
            if (transaction == null) {
                //没有查询到交易 不代表交易失败 可能是刚刚广播出去
                log.info("ERC20交易未同步 txId={}", txId);
                bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                return bean;
            }

            Optional<TransactionReceipt> transactionReceipt;
            Boolean success;
            try {
                transactionReceipt = admin.ethGetTransactionReceipt(txId).send().getTransactionReceipt();
                success = transactionReceipt.map(TransactionReceipt::getStatus)
                        // 0x0失败 0x1成功 null失败或者未知
                        .map("0x1"::equals).get();
            } catch (Exception e) {
                log.info("ERC20交易未同步 txId={}", txId);
                bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
                return bean;
            }

            BigInteger confirmations = EthService.getNewestBlockNumber().subtract(transaction.getBlockNumber()).add(BigInteger.valueOf(1));
            bean.setConfirmations(confirmations.longValue());
            BigDecimal gasPrice = new BigDecimal(String.valueOf(transaction.getGasPrice()));
            BigInteger gasUsed = transactionReceipt.map(TransactionReceipt::getGasUsed).get();
            BigDecimal fee = gasPrice.multiply(BigDecimal.valueOf(gasUsed.longValue())).divide(BlockConstant.WEI, 8, ROUND_HALF_UP);
            log.info("ERC20交易矿工费={}", fee);
            bean.setFee(fee);
            bean.setBlockNumber(transaction.getBlockNumber().longValue());
            if (success) {
                bean.setStatus(TxStateEnum.SUCCESS.getStatus());
            } else {
                bean.setStatus(TxStateEnum.FAIL.getStatus());
            }
        } catch (IOException e) {
            //查询失败 不代表交易失败
            bean.setStatus(TxStateEnum.TO_BE_CONFIRMED.getStatus());
            log.error("ERC20交易查询error txId={} error={}", txId, e);
        }
        return bean;
    }

    private String sendErc20Token(BigInteger nonce, String to, BigDecimal value, BigDecimal fee, String privateKey, String contractAddress) throws BlockException {
        //加载转账所需的凭证，用私钥
        Credentials credentials = Credentials.create(privateKey);
        //手续费设置
        BigInteger gasPrice = Convert.toWei(fee, Convert.Unit.GWEI).toBigInteger();
        BigInteger gasLimit = BigInteger.valueOf(60000L);
        //创建RawTransaction交易对象
        if (contractAddress.equals(BlockConstant.USDT_ERC20_ADDRESS)) {
            value = value.multiply(BlockConstant.USDT_ERC20_RATIO);
        } else {
            value = value.multiply(BlockConstant.WEI);
        }
        BigInteger amount = value.toBigInteger();
        Function function = EthService.tranFunction(to, amount);
        String encodedFunction = FunctionEncoder.encode(function);
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit,
                contractAddress, encodedFunction);
        //签名Transaction，这里要对交易做签名
        byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signMessage);
        log.info("ERC20 Token 交易签名成功(不等于广播交易成功)");
        //发送交易
        return EthService.sendTx(hexValue);
    }

}
